# -*- coding: utf-8 -*-
import re

try:
    from collection import deque
except ImportError:
    deque = list

__all__ = ['hatenatext']

def hatenatext(text, encoding=None, output=None, baseuri=None, sectionanchor=None):
    """
    convert a given text marked up with Hatena-Text to html.
    the parameter encoding is the encoding of the text, and
    the parameter output is the encoding of the output html
    fragment.
    if the output encoding is not specified, then return the result 
    as unicode.
    """
    encoding = encoding or 'utf-8'

    if not isinstance(text, (str, unicode)):
        text = str(text)

    context = Context(text.split('\n'), baseuri, sectionanchor)
    writer = XhtmlWriter(encoding)

    BodyNode().parse(context, writer)
    html = writer.__unicode__()

    if output is None:
        return html
    else:
        return html.encode(output)

class XhtmlWriter(object):
    def __init__(self, encoding=None):
        self.buf = []
        self.encoding = encoding or 'utf-8'

    def _escape(self, value):
        from xml.sax.saxutils import escape
        
        if not isinstance(value, unicode):
            try:
                value = unicode(value, self.encoding)
            except UnicodeDecodeError:
                return u''
        
        return escape(value)

    def write(self, value):
        self.buf.append(self._escape(value))

    def write_html(self, value):
        assert isinstance(value, unicode)
        self.buf.append(value)

    def write_inline(self, start, value, end):
        assert isinstance(start, unicode)
        assert isinstance(end, unicode)
        value = self._escape(value)
        self.buf.append(u'%s%s%s' % (start, value, end))

    def write_cdata(self, value):
        if not isinstance(value, unicode):
            try:
                value = unicode(value, self.encoding)
            except UnicodeDecodeError:
                pass

        self.buf.append(value)

    def __str__(self):
        return self.__unicode__().encode(self.encoding)

    def __unicode__(self):
        return u'\n'.join(self.buf)

class Context(object):
    def __init__(self, lines, baseuri=None, sectionanchor=None):
        self.matches = deque()
        self.lines = lines
        self._num = 0
        self.footnotes = []
        self.sectioncount = 0
        self.noparagraph = False
        self.baseuri = baseuri or u''
        self.sectionanchor = sectionanchor or u''

    def push_match(self, match):
        self.matches.append(match)

    def pop_match(self):
        return self.matches.pop()

    def has_next(self):
        return len(self.lines) > self._num

    def currentline(self):
        try:
            return self.lines[self._num]
        except IndexError:
            print self.lines, self._num, self.has_next()
            raise

    def shift_line(self):
        value = self.lines[self._num]
        self._num += 1
        return value

    def skip_nodes(self, end_pattern):
        start = current = self._num
        while 1:
            try:
                line = self.lines[current]
            except IndexError:
                break

            if end_pattern.match(line):
                break
            current += 1

        nodes = self.lines[start:current]
        self._num = current
        
        return Context(nodes, self.baseuri)


class Node(object):
    pattern = None



class PNode(Node):
    def parse(self, context, writer):
        line = context.shift_line()
        writer.write_inline(u'<p>', line, u'</p>')


class CDataNode(Node):
    def parse(self, context, writer):
        line = context.shift_line()
        writer.write_cdata(line)


class BrNode(Node):
    def parse(self, context, writer):
        context.shift_line()
        if not context.has_next() or len(context.currentline()):
            return

        context.shift_line()
        writer.write_html(u'<br />')

class TaglineNode(Node):
    pattern = re.compile(r'^>(<.*>)<$')
    
    def parse(self, context, writer):
        m = context.pop_match()
        writer.write_cdata(m.groups()[0])
        context.shift_line()

class ListNode(Node):
    pattern = re.compile(r'^([\-\+]+)([^>\-\+].*)$')

    def parse(self, context, writer):
        m = context.pop_match()

        listtype, text = m.groups()
        llevel = len(listtype)
        list_tag = listtype[0] == '-' and u'ul' or u'ol'
        
        writer.write_html(u'<%s>' % list_tag)
        
        open = False
        while 1:
            if not context.has_next():
                break

            m = self.pattern.match(context.currentline())
            if not m:
                break

            listtype, text = m.groups()
            
            if len(listtype) > llevel:
                context.push_match(m)
                ListNode().parse(context, writer)
            elif len(listtype) < llevel:
                break
            else:
                if open:
                    open = False
                    writer.write_html(u'</li>')
                
                context.shift_line()
                
                if context.has_next():
                    nl = context.currentline()
                else:
                    nl = u''
            
                m = self.pattern.match(nl)
                if not m:
                    writer.write_inline(u'<li>', text, u'</li>')
                    continue

                if len(m.groups()[1]) > llevel:
                    open = True
                    writer.write_html(u'<li>')
                    writer.write(text)

        if open:
            writer.write_html(u'</li>')


        writer.write_html(u'</%s>' % list_tag)

class DlNode(Node):
    pattern = re.compile(r'^\:((?:<[^>]+>|\[\].+?\[\]|\[[^\]]+\]|\[\]|[^\:<\[]+)+)\:(.+)$')

    def parse(self, context, writer):
        m = context.pop_match()

        writer.write_html(u'<dl>')
        while 1:
            if not m:
                break
            context.shift_line()
            dt, dd = m.groups()
            writer.write_inline(u'<dt>', dt, u'</dt>')
            writer.write_inline(u'<dd>', dd, u'</dd>')
            
            if not context.has_next():
                break
            m = self.pattern.match(context.currentline())
        
        writer.write_html(u'</dl>')


class TableNode(Node):
    pattern = re.compile(r'^\|([^\|]*\|(?:[^\|]*\|)+)$')
    cell_pattern = re.compile(r'([^\|]+)\|')

    def parse(self, context, writer):
        m = context.pop_match()

        writer.write_html(u'<table>')
        while 1:
            if not self.pattern.match(context.currentline()):
                break

            line = context.shift_line()
            
            writer.write_html(u'<tr>')
            
            for x in self.cell_pattern.findall(line):
                if x and x[0] == '*':
                    writer.write_inline(u'<th>', x[1:], u'</th>')
                else:
                    writer.write_inline(u'<td>', x, u'</td>')

            writer.write_html(u'</tr>')

            if not context.has_next():
                break

        writer.write_html(u'</table>')


class PreNode(Node):
    pattern = re.compile(r'^>\|$')
    end_pattern  = re.compile(r'(.*?)\|<$')

    def parse(self, context, writer):

        context.shift_line()
        
        writer.write_html(u'<pre>')
        
        nested = context.skip_nodes(self.end_pattern)
        while 1:
            if not nested.has_next():
                break

            line = nested.shift_line()
            writer.write(line)
        
        close = u'</pre>'
        if context.has_next():
            m = self.end_pattern.match(context.shift_line())
            if m:
                # write the characters before |<
                close = '%s</pre>' % m.groups()[0]

        writer.write_cdata(close)


class SuperpreNode(Node):
    pattern = re.compile(r'^>\|(\w+|\?)?\|$')
    end_pattern = re.compile(r'^\|\|<$')

    def parse(self, context, writer):
        m = context.pop_match()

        context.shift_line()
        
        syntax_type = m.groups()[0]
        writer.write_html(u'<pre class="hatena-super-pre">')
        
        while 1:
            if not context.has_next():
                break
        
            if self.end_pattern.match(context.currentline()):
                context.shift_line()
                break

            line = context.shift_line()
            if syntax_type:
                # @TODO, syntax highlighting.
                html = line
            else:
                # @TODO, escape html chars.
                html = line
            writer.write(html)
        
        writer.write_html(u'</pre>')

class H3Node(Node):
    pattern = re.compile(r'^\*((?:[^\*]).*)$')

    def parse(self, context, writer):
        m = context.pop_match()
        writer.write_inline(u'<h3>', m.groups()[0], u'</h3>')
        context.shift_line()


class H3anchorNode(Node):
    pattern = re.compile(r'^\*(?:(\d{9,10}|[a-zA-Z]\w*)\*)?((?:\[[^\:\[\]]+\])+)?(.*)$')
    sa_pattern = re.compile(r'\[([^\:\[\]]+)\]')

    def parse(self, context, writer):
        import urllib
        m = context.pop_match()
        
        context.shift_line()
        name, cat, title = m.groups()

        # categories
        cats = []
        if cat:
            for x in self.sa_pattern.findall(cat):
                cats.append(u'<a href="?word=%s" class="sectioncategory">%s</a>' % (urllib.quote(x), x))

        # section anchor and timestamp
        sa, timestamp = self.format_name(name)

        writer.write_cdata(u'<h3><a href="#" name="%s"><span class="sanchor">%s</span> %s%s</a></h3>%s' % (
                                sa,
                                u'',
                                u''.join(cats),
                                title,
                                timestamp
                              ))

    def format_name(self, name):
        import time
        try:
            dt = time.localtime(int(name))
            return name, u' <span class="timestamp">%s:%s</span>' % (dt.tm_hour, dt.tm_min)
        except:
            return name, u''


class H4Node(Node):
    pattern = re.compile(r'^\*\*((?:[^\*]).*)$')

    def parse(self, context, writer):
        m = context.pop_match()
        context.shift_line()
        writer.write_inline(u'<h4>', m.groups()[0], u'</h4>')


class H5Node(Node):
    pattern = re.compile(r'^\*\*\*((?:[^\*]).*)$')

    def parse(self, context, writer):
        m = context.pop_match()
        context.shift_line()
        writer.write_inline(u'<h5>', m.groups()[0], u'</h5>')

class H6Node(Node):
    pattern = re.compile(r'^\*{4,}((?:[^\*]).*)$')

    def parse(self, context, writer):
        m = context.pop_match()
        context.shift_line()
        writer.write_inline(u'<h6>', m.groups()[0], u'</h6>')


class TagNode(Node):
    pattern = re.compile(r'^>(<.*)$')
    end_pattern = re.compile(r'^(.*>)<$')
    
    # H5, H4, Blockquote, Dl, List, Pre, Superpre, Table
    childnodes = None
    
    def parse(self, context, writer):
        m = context.pop_match()

        line = context.shift_line()
        tag = m.groups()[0]
        writer.write_cdata(tag)
        
        nested_context = context.skip_nodes(self.end_pattern)
        nested_context.noparagraph = True
        while 1:
            node = find_node(nested_context, self.childnodes)
            if not node:
                break
            node.parse(nested_context, writer)

        line = context.shift_line()
        m = self.end_pattern.match(line)
        if m:
            writer.write_cdata(m.groups()[0])


class SectionNode(Node):
    # H5, H4, H3, H3Anchor, Blockquote, Dl, List, Superpre, Pre, Table, Tagline, Tag
    childnodes = None

    def parse(self, context, writer):
        context.sectioncount += 1
        writer.write_html(u'<div class="section">')
        while 1:
            node = find_node(context, self.childnodes)
            if not node:
                break
            
            node.parse(context, writer)
        writer.write_html(u'</div>')


class BlockquoteNode(SectionNode):
    pattern = re.compile(r'^>>$')
    end_pattern = re.compile(r'^<<$')
    
    # H5, H4, Blockquote, Dl, List, Pre, Superpre, Table, Tagline, Tag
    childnodes = None

    def parse(self, context, writer):
        m = context.pop_match()
        context.shift_line()
        writer.write_html(u'<blockquote>')
        
        while 1:
            if not context.has_next():
                break
            
            line = context.currentline()
            if self.end_pattern.match(line):
                context.shift_line()
                break
            
            node = find_node(context, self.childnodes)
            if node:
                node.parse(context, writer)
        
        writer.write_html(u'</blockquote>')

class BodyNode(Node):
    def parse(self, context, writer):
        SectionNode().parse(context, writer)


def find_node(context, candidates):
    if not context.has_next():
        return None

    line = context.currentline()
    for n in candidates:
        m = n.pattern.match(line)
        if m:
            context.push_match(m)
            return n()

    if not len(line):
        return BrNode()
    elif context.noparagraph:
        return CDataNode()
    else:
        return PNode()

SectionNode.childnodes = [
    H5Node, H4Node, H3Node, BlockquoteNode, DlNode, ListNode, SuperpreNode, PreNode, TableNode, TaglineNode, TagNode
]

TagNode.childnodes = [
    H5Node, H4Node, BlockquoteNode, DlNode, ListNode, PreNode, SuperpreNode, TableNode
]

BlockquoteNode.childnodes = [
    H5Node, H4Node, BlockquoteNode, DlNode, ListNode, PreNode, SuperpreNode, TableNode, TaglineNode, TagNode
]